
// Does this script currently respond to Input?
var canControl = true;

// The character will spawn at spawnPoint's position when needed.  This could be changed via a script at runtime to implement, e.g. waypoints/savepoints.
var spawnPoint : Transform;

class PlatformerControllerMovement {
    // The speed when walking
    var walkSpeed = 3.0;
    // when pressing "Fire1" button (control) we start running
    var runSpeed = 10.0;

    var inAirControlAcceleration = 1.0;

    // The gravity for the character
    var gravity = 60.0;
    var maxFallSpeed = 20.0;

    // How fast does the character change speeds?  Higher is faster.
    var speedSmoothing = 5.0;

    // This controls how fast the graphics of the character "turn around" when the player turns around using the controls.
    var rotationSmoothing = 10.0;

    // The current move direction in x-y.  This will always been (1,0,0) or (-1,0,0)
    // The next line, @System.NonSerialized , tells Unity to not serialize the variable or show it in the inspector view.  Very handy for organization!
    @System.NonSerialized
    var direction = Vector3.zero;

    // The current vertical speed
    @System.NonSerialized
    var verticalSpeed = 0.0;

    // The current movement speed.  This gets smoothed by speedSmoothing.
    @System.NonSerialized
    var speed = 0.0;

    // Is the user pressing the left or right movement keys?
    @System.NonSerialized
    var isMoving = false;

    // The last collision flags returned from controller.Move
    @System.NonSerialized
    var collisionFlags : CollisionFlags;

    // We will keep track of an approximation of the character's current velocity, so that we return it from GetVelocity () for our camera to use for prediction.
    @System.NonSerialized
    var velocity : Vector3;
   
    // This keeps track of our current velocity while we're not grounded?
    @System.NonSerialized
    var inAirVelocity = Vector3.zero;

    // This will keep track of how long we have we been in the air (not grounded)
    @System.NonSerialized
    var hangTime = 0.0;
}

var movement : PlatformerControllerMovement;

// We will contain all the jumping related variables in one helper class for clarity.
class PlatformerControllerJumping {
    // Can the character jump?
    var enabled = true;

    // How high do we jump when pressing jump and letting go immediately
    var height = 1.0;
    // We add extraHeight units (meters) on top when holding the button down longer while jumping
    var extraHeight = 4.1;
   
    var doubleJumpHeight = 2.1;
   
    // This prevents inordinarily too quick jumping
    // The next line, @System.NonSerialized , tells Unity to not serialize the variable or show it in the inspector view.  Very handy for organization!
    @System.NonSerialized
    var repeatTime = 0.05;

    @System.NonSerialized
    var timeout = 0.15;

    // Are we jumping? (Initiated with jump button and not grounded yet)
    @System.NonSerialized
    var jumping = false;
   
    // Are where double jumping? ( Initiated when jumping or falling after pressing the jump button )
    @System.NonSerialized
    var doubleJumping = false;
   
    // Can we make a double jump ( we can't make two double jump or more at the same jump )
    @System.NonSerialized
    var canDoubleJump = false;
   
    @System.NonSerialized
    var reachedApex = false;
 
    // Last time the jump button was clicked down
    @System.NonSerialized
    var lastButtonTime = -10.0;
   
    // Last time we performed a jump
    @System.NonSerialized
    var lastTime = -1.0;

    // the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)
    @System.NonSerialized
    var lastStartHeight = 0.0;
}

var jump : PlatformerControllerJumping;

private var controller : CharacterController;

// Moving platform support.
private var activePlatform : Transform;
private var activeLocalPlatformPoint : Vector3;
private var activeGlobalPlatformPoint : Vector3;
private var lastPlatformVelocity : Vector3;

// This is used to keep track of special effects in UpdateEffects ();
private var areEmittersOn = false;

function Awake () {
    movement.direction = transform.TransformDirection (Vector3.forward);
    controller = GetComponent (CharacterController);
    Spawn ();
}

function Spawn () {
    // reset the character's speed
    movement.verticalSpeed = 0.0;
    movement.speed = 0.0;
   
    // reset the character's position to the spawnPoint
    transform.position = spawnPoint.position;
   
}

function OnDeath () {
    Spawn ();
}

function UpdateSmoothedMovementDirection () {   
    var h = Input.GetAxisRaw ("Horizontal");
   
    if (!canControl)
        h = 0.0;
   
    movement.isMoving = Mathf.Abs (h) > 0.1;
       
    if (movement.isMoving)
        movement.direction = Vector3 (h, 0, 0);
   
    // Grounded controls
    if (controller.isGrounded) {
        // Smooth the speed based on the current target direction
        var curSmooth = movement.speedSmoothing * Time.deltaTime;
       
        // Choose target speed
        var targetSpeed = Mathf.Min (Mathf.Abs(h), 1.0);
   
        // Pick speed modifier
        if (Input.GetButton ("Fire2") && canControl)
            targetSpeed *= movement.runSpeed;
        else
            targetSpeed *= movement.walkSpeed;
       
        movement.speed = Mathf.Lerp (movement.speed, targetSpeed, curSmooth);
       
        movement.hangTime = 0.0;
    }
    else {
        // In air controls
        movement.hangTime += Time.deltaTime;
        if (movement.isMoving)
            movement.inAirVelocity += Vector3 (Mathf.Sign(h), 0, 0) * Time.deltaTime * movement.inAirControlAcceleration;
    }
}

function FixedUpdate () {
    // Make sure we are absolutely always in the 2D plane.
    transform.position.z = 0;

}

function ApplyJumping () {
    // Prevent jumping too fast after each other
    if (jump.lastTime + jump.repeatTime > Time.time)
        return;

    if (controller.isGrounded) {
        // Jump
        // - Only when pressing the button down
        // - With a timeout so you can press the button slightly before landing  
        if (jump.enabled && Time.time < jump.lastButtonTime + jump.timeout) {
            movement.verticalSpeed = CalculateJumpVerticalSpeed (jump.height);
            movement.inAirVelocity = lastPlatformVelocity;
            SendMessage ("DidJump", SendMessageOptions.DontRequireReceiver);
        }
    }
}

function ApplyGravity () {
    // Apply gravity
    var jumpButton = Input.GetButton ("Jump");
   
   
   
    if (!canControl)
        jumpButton = false;
   
    // When we reach the apex of the jump we send out a message
    if (jump.jumping && !jump.reachedApex && movement.verticalSpeed <= 0.0) {
        jump.reachedApex = true;
        SendMessage ("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
    }
   
    // if we are jumping and we press jump button, we do a double jump or
    // if we are falling, we can do a double jump to
    if ( ( jump.jumping  && Input.GetButtonUp("Jump") && !jump.doubleJumping )  || ( !controller.isGrounded  && !jump.jumping && !jump.doubleJumping && movement.verticalSpeed  < -12.0  )  ) {
                    jump.canDoubleJump = true;
    }
   
    // if we can do a double jump, and we press the jump button, we do a double jump
    if ( jump.canDoubleJump && Input.GetButtonDown("Jump") && !IsTouchingCeiling () ){
        jump.doubleJumping=true;
        movement.verticalSpeed = CalculateJumpVerticalSpeed( jump.doubleJumpHeight );
        jump.canDoubleJump = false;
   
    }
    // * When jumping up we don't apply gravity for some time when the user is holding the jump button
    //   This gives more control over jump height by pressing the button longer
    var extraPowerJump =  jump.jumping && !jump.doubleJumping && movement.verticalSpeed > 0.0 && jumpButton && transform.position.y < jump.lastStartHeight + jump.extraHeight && !IsTouchingCeiling ();
   
    if (extraPowerJump)
        return;
    else if (controller.isGrounded){
        movement.verticalSpeed = -movement.gravity * Time.deltaTime;
        jump.canDoubleJump = false;
    }
    else
        movement.verticalSpeed -= movement.gravity * Time.deltaTime;
       
    // Make sure we don't fall any faster than maxFallSpeed.  This gives our character a terminal velocity.
    movement.verticalSpeed = Mathf.Max (movement.verticalSpeed, -movement.maxFallSpeed);
}

function CalculateJumpVerticalSpeed (targetJumpHeight : float) {
    // From the jump height and gravity we deduce the upwards speed
    // for the character to reach at the apex.
    return Mathf.Sqrt (2 * targetJumpHeight * movement.gravity);
}

function DidJump () {
    jump.jumping = true;
    jump.reachedApex = false;
    jump.lastTime = Time.time;
    jump.lastStartHeight = transform.position.y;
    jump.lastButtonTime = -10;
}

function UpdateEffects () {
    wereEmittersOn = areEmittersOn;
    areEmittersOn = jump.jumping && movement.verticalSpeed > 0.0;
   
    // By comparing the previous value of areEmittersOn to the new one, we will only update the particle emitters when needed
    if (wereEmittersOn != areEmittersOn) {
        for (var emitter in GetComponentsInChildren (ParticleEmitter)) {
            emitter.emit = areEmittersOn;
        }
    }
}

function Update () {
    if (Input.GetButtonDown ("Jump") && canControl) {
        jump.lastButtonTime = Time.time;
    }

    UpdateSmoothedMovementDirection();
   
    // Apply gravity
    // - extra power jump modifies gravity
    ApplyGravity ();

    // Apply jumping logic
    ApplyJumping ();
   
    // Moving platform support
    if (activePlatform != null) {
        var newGlobalPlatformPoint = activePlatform.TransformPoint(activeLocalPlatformPoint);
        var moveDistance = (newGlobalPlatformPoint - activeGlobalPlatformPoint);
        transform.position = transform.position + moveDistance;
        lastPlatformVelocity = (newGlobalPlatformPoint - activeGlobalPlatformPoint) / Time.deltaTime;
    } else {
        lastPlatformVelocity = Vector3.zero;   
    }
   
    activePlatform = null;
   
    // Save lastPosition for velocity calculation.
    lastPosition = transform.position;
   
    // Calculate actual motion
    var currentMovementOffset = movement.direction * movement.speed + Vector3 (0, movement.verticalSpeed, 0) + movement.inAirVelocity;
   
    // We always want the movement to be framerate independent.  Multiplying by Time.deltaTime does this.
    currentMovementOffset *= Time.deltaTime;
   
    // Move our character!
    movement.collisionFlags = controller.Move (currentMovementOffset);
   
    // Calculate the velocity based on the current and previous position. 
    // This means our velocity will only be the amount the character actually moved as a result of collisions.
    movement.velocity = (transform.position - lastPosition) / Time.deltaTime;
   
    // Moving platforms support
    if (activePlatform != null) {
        activeGlobalPlatformPoint = transform.position;
        activeLocalPlatformPoint = activePlatform.InverseTransformPoint (transform.position);
    }
   
    // Set rotation to the move direction   
    if (movement.direction.sqrMagnitude > 0.01)
        transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation (movement.direction), Time.deltaTime * movement.rotationSmoothing);
   
    // We are in jump mode but just became grounded
    if (controller.isGrounded) {
        movement.inAirVelocity = Vector3.zero;
        if (jump.jumping) {
            jump.jumping = false;
            jump.doubleJumping = false;
            jump.canDoubleJump = false;
            SendMessage ("DidLand", SendMessageOptions.DontRequireReceiver);

            var jumpMoveDirection = movement.direction * movement.speed + movement.inAirVelocity;
            if (jumpMoveDirection.sqrMagnitude > 0.01)
                movement.direction = jumpMoveDirection.normalized;
        }
    }   

    // Update special effects like rocket pack particle effects
    UpdateEffects ();
}

function OnControllerColliderHit (hit : ControllerColliderHit)
{
    if (hit.moveDirection.y > 0.01)
        return;
   
    // Make sure we are really standing on a straight platform
    // Not on the underside of one and not falling down from it either!
    if (hit.moveDirection.y < -0.9 && hit.normal.y > 0.9) {
        activePlatform = hit.collider.transform;   
    }
}

// Various helper functions below:
function GetSpeed () {
    return movement.speed;
}

function GetVelocity () {
    return movement.velocity;
}


function IsMoving () {
    return movement.isMoving;
}

function IsJumping () {
    return jump.jumping;
}

function IsDoubleJumping () {
    return jump.doubleJumping;
}

function canDoubleJump () {
    return jump.canDoubleJump;
}

function IsTouchingCeiling () {
    return (movement.collisionFlags & CollisionFlags.CollidedAbove) != 0;
}

function GetDirection () {
    return movement.direction;
}

function GetHangTime() {
    return movement.hangTime;
}

function Reset () {
    gameObject.tag = "Player";
}

function SetControllable (controllable : boolean) {
    canControl = controllable;
}

// Require a character controller to be attached to the same game object
@script RequireComponent (CharacterController)
@script AddComponentMenu ("2D Platformer/Platformer Controller")